﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using KC.Business.Bugs;
using KC.DataAccess.BuggerAll;
using KC.DataAccess.MV6;

namespace KC.Logging
{
	/// <summary>Handles all error logging within the KC Architecture.</summary>
	public static class Logger
	{
		public enum Severity
		{
			Invalid = 0,
			Information = 1,
			Warning = 2,
			Error = 3,
			Critical = 4
		}

		/// <summary>Writes a DebugInfo to the BuggerAll database.</summary>
		/// <param name="ex">Exception</param>
		/// <param name="sev">Severity of error</param>
		/// <returns>DebugInfo ID</returns>
		public static int Write(int ProjectID, Exception ex, Severity sev)
		{
			if (ex == null)
				throw new ArgumentNullException("Can't write null exception to error log", ex);

			// Concatenate recursive InnerExceptions
			string detail = ex.Message;
			Exception ex2 = ex;
			if (ex.InnerException != null)
			{
				while (ex2.InnerException != null)
				{
					detail += "\r\nInnerException: " + ex2.InnerException.Message;
					ex2 = ex2.InnerException;
				}
			}
			detail += "\r\n" + ex.ToString();

			return WriteInternal(ProjectID, ex.Message, detail, sev).ID;
		}
		/// <summary>Writes a DebugInfo to the BuggerAll database.</summary>
		/// <param name="Description">Error description</param>
		/// <param name="Detail">Error detail</param>
		/// <param name="Sev">Severity of error</param>
		/// <returns>DebugInfo ID</returns>
		public static int Write(int ProjectID, string Description, string Detail, Severity Sev)
		{
			if (string.IsNullOrEmpty(Description)) throw new ArgumentNullException("Description");
			if (string.IsNullOrEmpty(Detail)) throw new ArgumentNullException("Detail");
			return WriteInternal(ProjectID, Description ?? "(Null description)", Detail, Sev).ID;
		}
		/// <summary>Writes a DebugInfo to the BuggerAll database.</summary>
		/// <param name="ex">Exception</param>
		/// <param name="sev">Severity of error</param>
		/// <returns>DebugInfo ID</returns>
		public static int Write(string ProjectName, Exception ex, Severity sev)
		{
			if (string.IsNullOrEmpty(ProjectName)) throw new ArgumentNullException("ProjectName");
			if (ex == null) throw new ArgumentNullException("ex");

			// Concatenate recursive InnerExceptions
			string detail = ex.Message;
			Exception ex2 = ex;
			if (ex.InnerException != null)
			{
				while (ex2.InnerException != null)
				{
					detail += "\r\nInnerException: " + ex2.InnerException.Message;
					ex2 = ex2.InnerException;
				}
			}
			detail += "\r\n" + ex.ToString();
			try { return WriteInternal(ProjectName, ex.Message, detail, sev).ID; }
			catch { return -1; }
		}
		/// <summary>Writes a DebugInfo to the BuggerAll database.</summary>
		/// <param name="Description">Error description</param>
		/// <param name="Detail">Error detail</param>
		/// <param name="Sev">Severity of error</param>
		/// <returns>DebugInfo ID</returns>
		public static int Write(string ProjectName, string Description, string Detail, Severity Sev)
		{
			if (string.IsNullOrEmpty(ProjectName)) throw new ArgumentNullException("ProjectName");
			if (string.IsNullOrEmpty(Description)) throw new ArgumentNullException("Description");
			if (string.IsNullOrEmpty(Detail)) throw new ArgumentNullException("Detail");
			return WriteInternal(ProjectName, Description, Detail, Sev).ID;
		}
		/// <summary>Writes an Error to the MV6 database.</summary>
		/// <param name="ex">Exception or error</param>
		/// <param name="InnerException">Inner Exception, if any</param>
		/// <param name="AID">Activity ID</param>
		public static void WriteMV6(string ex, Exception InnerException = null, int AID = -1)
		{
			using (MV6DataContext dc = new MV6DataContext())
			{
				Error err = new Error();
				err.AID = AID;
				err.Ex = ex;
				err.InnerException = InnerException.ToString();
				err.Location = Environment.StackTrace;
				err.datetime = DateTime.Now;
				dc.Errors.InsertOnSubmit(err);
				dc.SubmitChanges();
			}
		}
		public static void WriteMV6(string ex, string InnerException = null, int AID = -1)
		{
			using (MV6DataContext dc = new MV6DataContext())
			{
				Error err = new Error();
				err.AID = AID;
				err.Ex = ex;
				err.InnerException = InnerException;
				err.Location = Environment.StackTrace;
				err.datetime = DateTime.Now;
				dc.Errors.InsertOnSubmit(err);
				dc.SubmitChanges();
			}
		}

		/// <summary>Opens a bug against this DebugInfo. Should only be used when a condition has 
		/// occurred that should never occur by design.</summary>
		/// <param name="di">DebugInfo object</param>
		private static DebugInfo WriteInternal(int ProjectID, string Description, string Detail, Severity Sev)
		{
			if (ProjectID == -1)
				throw new ArgumentOutOfRangeException("ProjectID");
			if (string.IsNullOrEmpty(Description))
				throw new ArgumentNullException("Description", "Can't write a DebugInfo without the non-nullable Description!");

			DebugInfo di = new DebugInfo();
			di.Severity = (Int16)Sev;
			di.ProjectID = ProjectID;
			di.DateTime = DateTime.Now;
			di.Description = Description;
			di.Detail = Detail;
			di.ProjectID = ProjectID;
			di.StackTrace = FormatStackTrace(Environment.StackTrace);

			BuggerAllDataContext dc = new BuggerAllDataContext();
			dc.DebugInfos.InsertOnSubmit(di);
			dc.SubmitChanges();
			return di;
		}
		/// <summary>Opens a bug against this DebugInfo. Should only be used when a condition has 
		/// occurred that should never occur by design.</summary>
		/// <param name="di">DebugInfo object</param>
		private static DebugInfo WriteInternal(string ProjectName, string Description, string Detail, Severity Sev)
		{
			if (string.IsNullOrEmpty(ProjectName))
				throw new ArgumentNullException("ProjectName", "Need to know what Project to log this error to!");
			if (string.IsNullOrEmpty(Description))
				throw new ArgumentNullException("Description", "Can't write a DebugInfo without the non-nullable Description!");

			BuggerAllDataContext dc = new BuggerAllDataContext();
			var projects = from p in dc.Projects where p.Name.ToLower() == ProjectName.ToLower() select p;
			if (projects.Count() == 0) throw new ArgumentOutOfRangeException("ProjectName", "No project exists with this name");
			if (projects.Count() > 1) throw new ArgumentOutOfRangeException("ProjectName", projects.Count().ToString()+" projects exist with this name. Try passing in by ID or fix the BuggerAll database!");

			int ProjectID = projects.First().ID;

			DebugInfo di = new DebugInfo();
			di.Severity = (Int16)Sev;
			di.ProjectID = ProjectID;
			di.DateTime = DateTime.Now;
			di.Description = Description;
			di.Detail = Detail;
			di.StackTrace = FormatStackTrace(Environment.StackTrace);

			dc.DebugInfos.InsertOnSubmit(di);
			dc.SubmitChanges();

			return di;
		}
		/// <summary>Finds existing bugs that match this DebugInfo.</summary>
		/// <param name="di">DebugInfo to scan for</param>
		/// <returns>List of duplicate bug(s)</returns>
		private static List<Bug> FindExistingBugs(DebugInfo di)
		{
			BuggerAllDataContext dc = new BuggerAllDataContext();
			var Bugs = from b in dc.Bugs
					   where b.Description == di.Description
					   && b.ProjectID == di.ProjectID
					   select b;
			if (Bugs.Count() == 0)
				return null;
			return Bugs.ToList<Bug>();
		}
		/// <summary>Formats a stack trace for insertion into a DebugInfo object.</summary>
		/// <param name="StackTrace">Stack trace to format</param>
		/// <returns>Formatted stack trace</returns>
		public static string FormatStackTrace(string StackTrace)
		{
			if (string.IsNullOrEmpty(StackTrace))
				throw new ArgumentNullException("StackTrace", "Stack trace is empty; nothing to format!");

			StackTrace = StackTrace
				.Replace("\r\n  at ", "  at")
				.Replace("  at", "\r\n  at");
			string[] lines = StackTrace.Replace("\r", "").Split('\n');
			for (int i = 0; i < lines.Length; i++)
				lines[i] = lines[i].Trim();
			return
				string.Join("\r\n", lines)
				.Replace("\r\n\r\n", "\r\n")
				.Trim();
		}
	}
}
